Example: Monthly electricity sales for Virginia
Briefly characterize the dataset
library(arrow)
esales <- read_feather("esales.feather")
print(esales) # print the data as a table
summary(esales) # compute basic summary statistics about the data
value date year month
Min. : 7153 Min. :2001-01-01 Min. :2001 Min. : 1.000
1st Qu.: 8200 1st Qu.:2005-11-01 1st Qu.:2005 1st Qu.: 3.000
Median : 9019 Median :2010-09-01 Median :2010 Median : 6.000
Mean : 9093 Mean :2010-08-31 Mean :2010 Mean : 6.425
3rd Qu.: 9885 3rd Qu.:2015-07-01 3rd Qu.:2015 3rd Qu.: 9.000
Max. :11724 Max. :2020-05-01 Max. :2020 Max. :12.000
boxplot(esales)

Plot the time series

# library(lubridate) # Make it easy to deal with dates
esales %>% filter(month==3) # These three lines of code
esales %>% filter(month(date)==3) # all do
esales %>% filter(lubridate::month(date)==3) # the same thing.
# We don't have to keep the 'year' and 'month' column: can recover them if needed
esales %>%
select(date, sales_GWh = value) -> esales_tbl
print(esales_tbl)
Convert the data frame into a time series tsibble object
Make some plots
Make a histogram of monthly sales
hist(elsales_tbl_ts$sales_GWh, breaks=40)

Make a seasonal plot
# This plot won't work. Why not?
elsales_tbl_ts %>%
feasts::gg_season(sales_GWh, labels = "both") + ylab("Virginia electricity sales (GWh)")
autoplot(vaelsales_tbl_ts, sales_GWh) +
ylab("Virginia monthly total electricity sales (GWh)") +
xlab("")

Seasonal plots and seasonal subseries plots
vaelsales_tbl_ts %>% gg_season(sales_GWh, labels = "both") + ylab("Virginia electricity sales (GWh)")

vaelsales_tbl_ts %>%
gg_subseries(sales_GWh) +
labs(
y = "Sales (GWh)",
title = "Seasonal subseries plot: Virginia electricity sales"
)

Scatterplots
Investigating relationships between two variables. Scatterplots. Correlation. Scatterplot matrices.
Readings: FPP Sect. 2.6
vic_elec
summary(vic_elec)
Time Demand Temperature Date Holiday
Min. :2012-01-01 00:00:00 Min. :2858 Min. : 1.50 Min. :2012-01-01 Mode :logical
1st Qu.:2012-09-30 22:52:30 1st Qu.:3969 1st Qu.:12.30 1st Qu.:2012-09-30 FALSE:51120
Median :2013-07-01 22:45:00 Median :4635 Median :15.40 Median :2013-07-01 TRUE :1488
Mean :2013-07-01 22:45:00 Mean :4665 Mean :16.27 Mean :2013-07-01
3rd Qu.:2014-04-01 23:37:30 3rd Qu.:5244 3rd Qu.:19.40 3rd Qu.:2014-04-01
Max. :2014-12-31 23:30:00 Max. :9345 Max. :43.20 Max. :2014-12-31
vic_elec %>%
filter(year(Time) == 2013) %>%
autoplot(Demand) +
labs(
y = "Demand (GW)",
title = "Half-hourly electricity demand: Victoria"
)

vic_elec %>%
filter(year(Time) == 2013) %>%
autoplot(Temperature) +
labs(
y = "Temperature (degrees Celsius)",
title = "Half-hourly temperatures: Melbourne, Australia"
)

vic_elec %>%
filter(year(Time) == 2013) %>%
ggplot(aes(x = Temperature, y = Demand)) +
# geom_density2d() +
geom_point(size=0.1, aes(colour=Holiday), alpha = 0.4) +
labs(y = "Demand (GW)", x = "Temperature (degrees Celsius)")

A Scatterplot matrix

Example: Australian production
# install.packages('tsibbledata')
library(tsibbledata)
aus_production
aus_production %>% gg_season(Electricity)

aus_production %>% gg_season(Beer)

Example: Gross Domestic Product data
Exploratory data analysis
library(tsibbledata) # Data sets package
print(global_economy)
global_economy %>% filter(Country=="Sweden") %>% print()
global_economy %>%
filter(Country=="Sweden") %>%
autoplot(GDP) +
ggtitle("GDP for Sweden") + ylab("$US billions")

Fitting data to simple models
global_economy %>% model(trend_model = TSLM(GDP ~ trend())) -> fit
7 errors (1 unique) encountered for trend_model
[7] 0 (non-NA) cases
fit
NA
NA
fit %>% filter(Country == "Sweden") %>% residuals()
fit %>% filter(Country == "Sweden") %>% residuals() %>% autoplot(.resid)

Work with ln(GDP)
global_economy %>%
filter(Country=="Sweden") %>%
autoplot(log(GDP)) +
ggtitle("ln(GDP) for Sweden") + ylab("$US billions")

global_economy %>%
model(trend_model = TSLM(log(GDP) ~ trend())) -> logfit
7 errors (1 unique) encountered for trend_model
[7] 0 (non-NA) cases
logfit %>% filter(Country == "Sweden") %>% residuals() %>% autoplot()
Plot variable not specified, automatically selected `.vars = .resid`

global_economy %>% model(trend_model = TSLM(log(GDP) ~ log(Population))) -> fit3
7 errors (1 unique) encountered for trend_model
[7] 0 (non-NA) cases
fit3 %>% filter(Country == "Sweden") %>% residuals() %>% autoplot()
Plot variable not specified, automatically selected `.vars = .resid`

Producing forecasts
fit %>% forecast(h = "3 years") -> fcast3yrs
fcast3yrs
NA
fcast3yrs %>% filter(Country == "Sweden", Year == 2020) %>% str()
fable [1 × 5] (S3: fbl_ts/tbl_ts/tbl_df/tbl/data.frame)
$ Country: Factor w/ 263 levels "Afghanistan",..: 232
$ .model : chr "trend_model"
$ Year : num 2020
$ GDP : dist [1:1]
..$ 3:List of 2
.. ..$ mu : num 5.45e+11
.. ..$ sigma: num 5.34e+10
.. ..- attr(*, "class")= chr [1:2] "dist_normal" "dist_default"
..@ vars: chr "GDP"
$ .mean : num 5.45e+11
- attr(*, "key")= tibble [1 × 3] (S3: tbl_df/tbl/data.frame)
..$ Country: Factor w/ 263 levels "Afghanistan",..: 232
..$ .model : chr "trend_model"
..$ .rows : list<int> [1:1]
.. ..$ : int 1
.. ..@ ptype: int(0)
..- attr(*, ".drop")= logi TRUE
- attr(*, "index")= chr "Year"
..- attr(*, "ordered")= logi TRUE
- attr(*, "index2")= chr "Year"
- attr(*, "interval")= interval [1:1] 1Y
..@ .regular: logi TRUE
- attr(*, "response")= chr "GDP"
- attr(*, "dist")= chr "GDP"
- attr(*, "model_cn")= chr ".model"
fcast3yrs %>%
filter(Country=="Sweden") %>%
autoplot(global_economy) +
ggtitle("GDP for Sweden") + ylab("$US billions")

Model residuals vs. forecast errors
Model residuals:
Your data: \(y_1, y_2, \ldots, y_T\)
Fitted values: \(\hat{y}_1, \hat{y}_2, \ldots, \hat{y}_T\)
Model residuals: \(e_t = y_t - \hat{y}_t\)
Forecast errors:
augment(fit)
augment(fit) %>% filter(Country == "Sweden") %>%
ggplot(aes(x = .resid)) +
geom_histogram(bins = 20) +
ggtitle("Histogram of residuals")

Example: GDP, several countries
library(tsibbledata) # Data sets package
nordic <- c("Sweden", "Denmark", "Norway", "Finland")
(global_economy %>% filter(Country %in% nordic) -> nordic_economy)
NA
nordic_economy %>% autoplot(GDP)

fitnord <- nordic_economy %>%
model(
trend_model = TSLM(GDP ~ trend()),
trend_model_ln = TSLM(log(GDP) ~ trend()),
ets = ETS(GDP ~ trend("A")),
arima = ARIMA(GDP)
)
fitnord
fitnord %>%
select(arima) %>%
coef()
Denmark: ARMA(1,1)
Finland: MA(2)
Norway: MA(1)
Sweden: MA(2)
nordic_economy %>%
model(arima_constrained = ARIMA(GDP ~ pdq(1,0,2))) %>% select(arima_constrained) %>% coef()
It looks like you're trying to fully specify your ARIMA model but have not said if a constant should be included.
You can include a constant using `ARIMA(y~1)` to the formula or exclude it by adding `ARIMA(y~0)`.It looks like you're trying to fully specify your ARIMA model but have not said if a constant should be included.
You can include a constant using `ARIMA(y~1)` to the formula or exclude it by adding `ARIMA(y~0)`.It looks like you're trying to fully specify your ARIMA model but have not said if a constant should be included.
You can include a constant using `ARIMA(y~1)` to the formula or exclude it by adding `ARIMA(y~0)`.It looks like you're trying to fully specify your ARIMA model but have not said if a constant should be included.
You can include a constant using `ARIMA(y~1)` to the formula or exclude it by adding `ARIMA(y~0)`.4 errors (1 unique) encountered for arima_constrained
[4] Could not find an appropriate ARIMA model.
This is likely because automatic selection does not select models with characteristic roots that may be numerically unstable.
For more details, refer to https://otexts.com/fpp3/arima-r.html#plotting-the-characteristic-roots
fitnord %>% coef()
fitnord %>% glance()
fitnord %>% filter(Country == "Denmark") %>% select(arima) %>% report()
Series: GDP
Model: ARIMA(1,1,1)
Coefficients:
ar1 ma1
-0.3898 0.7240
s.e. 0.2061 0.1434
sigma^2 estimated as 2.407e+20: log likelihood=-1417.5
AIC=2840.99 AICc=2841.45 BIC=2847.12
fitnord %>%
accuracy() %>%
arrange(Country, MPE)
# ETS forecasts
USAccDeaths %>%
ets() %>%
forecast() %>%
autoplot()
str(taylor)
plot(taylor)
Plot lagged values
vaelsales_tbl_ts %>% filter(month(Month) %in% c(3,6,9,12)) %>% gg_lag(sales_GWh, lags = 1:2)
vaelsales_tbl_ts %>% filter(month(Month) == 1) %>% gg_lag(sales_GWh, lags = 1:2)
vaelsales_tbl_ts %>% ACF(sales_GWh) %>% autoplot()
# decompose(vaelsales_tbl_ts)
vaelsales_tbl_ts %>%
model(STL(sales_GWh ~ trend(window=21) + season(window='periodic'), robust = TRUE)) %>%
components() %>%
autoplot()
vaelsales_tbl_ts %>%
mutate(ln_sales_GWh = log(sales_GWh)) %>%
model(STL(ln_sales_GWh ~ trend(window=21) + season(window='periodic'),
robust = TRUE)) %>%
components() %>%
autoplot()
vaelsales_tbl_ts %>%
features(sales_GWh, feat_stl)
vaelsales_tbl_ts %>%
features(sales_GWh, feature_set(pkgs="feasts"))
References
LS0tCnRpdGxlOiAgICAgIkV4cGxvcmF0b3J5IGFuYWx5c2lzIG9mIHRpbWUgc2VyaWVzIGRhdGE6IEV4YW1wbGVzIgppbnN0aXR1dGU6ICJTWVMgNTU4MSBUaW1lIFNlcmllcyAmIEZvcmVjYXN0aW5nLCBTcHJpbmcgMjAyMSIgCmF1dGhvcjogICAgICJJbnN0cnVjdG9yOiBBcnRodXIgU21hbGwiCmRhdGU6ICAgICAgICJWZXJzaW9uIG9mIGByIFN5cy5EYXRlKClgIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDQKICAgIGNvZGVfZm9sZGluZzogc2hvdyAjIG9wdGlvbnM6IHNob3csIGhpZGUKICAgIGZpZ19jYXB0aW9uOiB5ZXMKICAjIGh0bWxfZG9jdW1lbnQ6CiAgIyAgICAgICBrZWVwX21kOiB5ZXMKICAjIHBkZl9kb2N1bWVudDogZGVmYXVsdApiaWJsaW9ncmFwaHk6IC9Vc2Vycy9BcnRodXIvR2l0UmVwb3MvVGVhY2hpbmcvdGltZS1zZXJpZXMvdHNlcmllcy5iaWIKbGluay1jaXRhdGlvbnM6IHllcwotLS0KCmBgYHtyIHNldCB1cCBjb2RpbmcgZW52aXJvbm1lbnQsIGluY2x1ZGU9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMgbGlicmFyeShkcGx5cikgLS0gZG9uJ3QgbmVlZCB0aGlzIGlmIHlvdSBhcmUgbG9hZGluZyB0aGUgZW50aXJlICd0aWR5dmVyc2UnIHN1aXRlCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGx1YnJpZGF0ZSkgIyBGb3IgZWFzeSBoYW5kbGluZyBvZiB0aW1lLWluZGV4ZWQgb2JqZWN0cwoKIyBpZighKCdmcHAzJyAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpKSkgaW5zdGFsbC5wYWNrYWdlcygnZnBwMycpCmxpYnJhcnkoZnBwMykKYGBgCgoKIyBFeGFtcGxlOiBNb250aGx5IGVsZWN0cmljaXR5IHNhbGVzIGZvciBWaXJnaW5pYQoKIyMgRXh0cmFjdCBkYXRhIGZyb20gcmVtb3RlIGRhdGFiYXNlCgpgYGB7ciBvcGVuIGNvbm5lY3Rpb24gdG8gZGF0YWJhc2UsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CiMgT3BlbiBjb25uZWN0aW9uIHRvIGEgcmVtb3RlIGRhdGFiYXNlCiMgTWFrZSBzdXJlIHlvdXIgVlBOIG5ldHdvcmsgY29ubmVjdGlvbiBpcyBhY3RpdmUgaWYgbmVlZGVkIQoKIyBpZighKCdSUG9zdGdyZVNRTCcgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSkpIGluc3RhbGwucGFja2FnZXMoJ1JQb3N0Z3JlU1FMJykKbGlicmFyeShSUG9zdGdyZVNRTCkKCiMgIm15X3Bvc3RncmVzX2NyZWRlbnRpYWxzLlIiIGNvbnRhaW5zIHRoZSBsb2ctaW4gaW5mb3JtYXRpb24Kc291cmNlKCIvVXNlcnMvQXJ0aHVyL0dpdFJlcG9zL1RlYWNoaW5nL215X3Bvc3RncmVzX2RiX2NyZWRlbnRpYWxzLlIiKQoKIyBPcGVuIGNvbm5lY3Rpb24KZGJfZHJpdmVyIDwtIGRiRHJpdmVyKCJQb3N0Z3JlU1FMIikKZGIgPC0gZGJDb25uZWN0KGRiX2RyaXZlcix1c2VyPXVzZXIsIHBhc3N3b3JkPXBhc3N3b3JkLGRibmFtZT0icG9zdGdyZXMiLCBob3N0PWhvc3QpCnJtKHBhc3N3b3JkKSAKCiMgY2hlY2sgdGhlIGNvbm5lY3Rpb246IElmIGZ1bmN0aW9uIHJldHVybnMgdmFsdWUgVFJVRSwgdGhlIGNvbm5lY3Rpb24gaXMgd29ya2luZwpkYkV4aXN0c1RhYmxlKGRiLCAibWV0YWRhdGEiKQpgYGAKCmBgYHtyIHJldHJpZXZlIGRhdGEgZnJvbSBkYiwgZXZhbD1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KZXNhbGVzIDwtIGRiR2V0UXVlcnkoZGIsJ1NFTEVDVCAqIGZyb20gZWlhX2VsZWNfc2FsZXNfdmFfYWxsX20nKSAjIFNRTCBjb2RlIHRvIHJldHJpZXZlIGRhdGEgZnJvbSBhIHRhYmxlIGluIHRoZSByZW1vdGUgZGF0YWJhc2UKIyBzdHIoZXNhbGVzKQplc2FsZXMgPC0gYXNfdGliYmxlKGVzYWxlcykgIyBDb252ZXJ0IGRhdGFmcmFtZSB0byBhICd0aWJibGUnIGZvciB0aWR5dmVyc2Ugd29yawojIHN0cihlc2FsZXMpCmBgYAoKYGBge3Igc2F2ZSBkYXRhIGluIEFwYWNoZSBBcnJvdyBmb3JtYXQsIGV2YWw9RkFMU0V9CiMgUmVmZXJlbmNlOiBodHRwczovL2Fycm93LmFwYWNoZS5vcmcvZG9jcy9yLwojIGlmKCEoJ2Fycm93JyAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpKSkgaW5zdGFsbC5wYWNrYWdlcygnYXJyb3cnKQpsaWJyYXJ5KGFycm93KQp3cml0ZV9mZWF0aGVyKGVzYWxlcywgImVzYWxlcy5mZWF0aGVyIikKYGBgCgpgYGB7ciBjbG9zZSBkYiBjb25uZWN0aW9uLCBldmFsPUZBTFNFfQojIENsb3NlIGNvbm5lY3Rpb24gLS0gdGhpcyBpcyBnb29kIHByYWN0aWNlCmRiRGlzY29ubmVjdChkYikKZGJVbmxvYWREcml2ZXIoZGJfZHJpdmVyKQpgYGAKCiMjIEJyaWVmbHkgY2hhcmFjdGVyaXplIHRoZSBkYXRhc2V0CgpgYGB7ciByZWFkIGluIGRhdGF9CmxpYnJhcnkoYXJyb3cpCmVzYWxlcyA8LSByZWFkX2ZlYXRoZXIoImVzYWxlcy5mZWF0aGVyIikKYGBgCgpgYGB7ciB9CnByaW50KGVzYWxlcykgICAgIyBwcmludCB0aGUgZGF0YSBhcyBhIHRhYmxlCnN1bW1hcnkoZXNhbGVzKSAgIyBjb21wdXRlIGJhc2ljIHN1bW1hcnkgc3RhdGlzdGljcyBhYm91dCB0aGUgZGF0YQpib3hwbG90KGVzYWxlcykKYGBgCgpgYGB7ciB1c2UgdGlkeXZlcnNlIHN5bnRheCB0byBwZXJmb3JtIHNvbWUgc2ltcGxlIGRhdGEgbWFuaXB1bGF0aW9uc30KIyBSZWZlcmVuY2VzOiBodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLywgaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnLwojIGZpbHRlcihkYXRhIG9iamVjdCwgY29uZGl0aW9uKSA6IHN5bnRheCBmb3IgZmlsdGVyKCkgY29tbWFuZAoKZXNhbGVzICU+JQogIGZpbHRlcih5ZWFyID09IDIwMTkpICU+JQogIGZpbHRlcih2YWx1ZSA+IDkwMDApICU+JQogIHByaW50KCkKCihlc2FsZXMgJT4lCiAgZ3JvdXBfYnkoeWVhcikgJT4lCiAgc3VtbWFyaXNlKFRvdGFsID0gc3VtKHZhbHVlKSkgLT4gdG90YWxfZXNhbGVzX2J5X3llYXIpCgplc2FsZXMgJT4lCiAgbXV0YXRlKHNhbGVzX1RXaCA9IHZhbHVlLzEwMDApICU+JQogIHNlbGVjdCgtdmFsdWUpCmBgYAoKIyMgUGxvdCB0aGUgdGltZSBzZXJpZXMKCmBgYHtyIHVzZSBnZ3Bsb3QyIHRvIGdlbmVyYXRlIGEgcGxvdH0KI1JlZmVyZW5jZTogaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvCgpnZ3Bsb3QoZGF0YT1lc2FsZXMsIGFlcyh4PWRhdGUseT12YWx1ZSkpICsKICBnZW9tX2xpbmUoKSArIHhsYWIoIlllYXIiKSArIHlsYWIoIlZpcmdpbmlhIG1vbnRobHkgdG90YWwgZWxlY3RyaWNpdHkgc2FsZXMgKEdXaCkiKQpgYGAKCmBgYHtyfQojIGxpYnJhcnkobHVicmlkYXRlKSAjIE1ha2UgaXQgZWFzeSB0byBkZWFsIHdpdGggZGF0ZXMKCmVzYWxlcyAlPiUgZmlsdGVyKG1vbnRoPT0zKSAgICAgICAgICAgICAgICAgICAjIFRoZXNlIHRocmVlIGxpbmVzIG9mIGNvZGUKZXNhbGVzICU+JSBmaWx0ZXIobW9udGgoZGF0ZSk9PTMpICAgICAgICAgICAgICMgICBhbGwgZG8KZXNhbGVzICU+JSBmaWx0ZXIobHVicmlkYXRlOjptb250aChkYXRlKT09MykgICMgICB0aGUgc2FtZSB0aGluZy4KCiMgV2UgZG9uJ3QgaGF2ZSB0byBrZWVwIHRoZSAneWVhcicgYW5kICdtb250aCcgY29sdW1uOiBjYW4gcmVjb3ZlciB0aGVtIGlmIG5lZWRlZAoKZXNhbGVzICU+JQogIHNlbGVjdChkYXRlLCBzYWxlc19HV2ggPSB2YWx1ZSkgLT4gZXNhbGVzX3RibAoKcHJpbnQoZXNhbGVzX3RibCkKYGBgCgojIyBDb252ZXJ0IHRoZSBkYXRhIGZyYW1lIGludG8gYSB0aW1lIHNlcmllcyBgdHNpYmJsZWAgb2JqZWN0CgoKYGBge3J9CiMgaW5zdGFsbC5wYWNrYWdlcygidHNpYmJsZSIpCmxpYnJhcnkodHNpYmJsZSkgIyBSZWZlcmVuY2U6IGh0dHBzOi8vdHNpYmJsZS50aWR5dmVydHMub3JnL2FydGljbGVzL2ludHJvLXRzaWJibGUuaHRtbAoKZXNhbGVzX3RibCAlPiUgYXNfdHNpYmJsZShpbmRleCA9IGRhdGUpIC0+IGVsc2FsZXNfdGJsX3RzCgpwcmludChlbHNhbGVzX3RibF90cykKYGBgCgojIyBNYWtlIHNvbWUgcGxvdHMKCiMjIyAgTWFrZSBhIGhpc3RvZ3JhbSBvZiBtb250aGx5IHNhbGVzCgpgYGB7ciBtYWtlIGEgaGlzdG9ncmFtIG9mIHRoZSBkYXRhfQpoaXN0KGVsc2FsZXNfdGJsX3RzJHNhbGVzX0dXaCwgYnJlYWtzPTQwKQpgYGAKCiMjIyAgTWFrZSBhIHNlYXNvbmFsIHBsb3QKCmBgYHtyLCBldmFsPUZBTFNFfQojIFRoaXMgcGxvdCB3b24ndCB3b3JrLiBXaHkgbm90PwplbHNhbGVzX3RibF90cyAlPiUgCiAgZmVhc3RzOjpnZ19zZWFzb24oc2FsZXNfR1doLCBsYWJlbHMgPSAiYm90aCIpICsgeWxhYigiVmlyZ2luaWEgZWxlY3RyaWNpdHkgc2FsZXMgKEdXaCkiKQpgYGAKCmBgYHtyIENoYW5nZSB0aGUgdHNpYmJsZSBzbyBpbmRleCBpcyBtb250aGx5fQojIGluc3RhbGwucGFja2FnZXMoImZlYXN0cyIpLCBSZWZlcmVuY2U6IGh0dHBzOi8vZmVhc3RzLnRpZHl2ZXJ0cy5vcmcvCmxpYnJhcnkoZmVhc3RzKQoKZWxzYWxlc190YmxfdHMgJT4lIAogIG11dGF0ZShNb250aCA9IHRzaWJibGU6OnllYXJtb250aChkYXRlKSkgJT4lIAogIGFzX3RzaWJibGUoaW5kZXggPSBNb250aCkgJT4lIAogIHNlbGVjdChNb250aCxzYWxlc19HV2gpIC0+IHZhZWxzYWxlc190YmxfdHMKCnByaW50KHZhZWxzYWxlc190YmxfdHMpCmBgYApgYGB7cn0KYXV0b3Bsb3QodmFlbHNhbGVzX3RibF90cywgc2FsZXNfR1doKSArIAogIHlsYWIoIlZpcmdpbmlhIG1vbnRobHkgdG90YWwgZWxlY3RyaWNpdHkgc2FsZXMgKEdXaCkiKSArIAogIHhsYWIoIiIpICAjIExlYXZlIGhvcml6LiBheGlzIGxhYmVsIGJsYW5rCmBgYAojIyBTZWFzb25hbCBwbG90cyBhbmQgc2Vhc29uYWwgc3Vic2VyaWVzIHBsb3RzCgpgYGB7ciAsIHdhcm5pbmc9RkFMU0V9CnZhZWxzYWxlc190YmxfdHMgJT4lIGdnX3NlYXNvbihzYWxlc19HV2gsIGxhYmVscyA9ICJib3RoIikgKyB5bGFiKCJWaXJnaW5pYSBlbGVjdHJpY2l0eSBzYWxlcyAoR1doKSIpCmBgYAoKCmBgYHtyfQp2YWVsc2FsZXNfdGJsX3RzICU+JSAKICBnZ19zdWJzZXJpZXMoc2FsZXNfR1doKSArCiAgbGFicygKICAgIHkgPSAiU2FsZXMgKEdXaCkiLAogICAgdGl0bGUgPSAiU2Vhc29uYWwgc3Vic2VyaWVzIHBsb3Q6IFZpcmdpbmlhIGVsZWN0cmljaXR5IHNhbGVzIgogICkKYGBgCgojIyBTY2F0dGVycGxvdHMKCkludmVzdGlnYXRpbmcgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHR3byB2YXJpYWJsZXMuIFNjYXR0ZXJwbG90cy4gQ29ycmVsYXRpb24uIFNjYXR0ZXJwbG90IG1hdHJpY2VzLgoKUmVhZGluZ3M6IEZQUCBTZWN0LiAyLjYKCmBgYHtyfQp2aWNfZWxlYwoKc3VtbWFyeSh2aWNfZWxlYykKCnZpY19lbGVjICU+JQogIGZpbHRlcih5ZWFyKFRpbWUpID09IDIwMTMpICU+JQogIGF1dG9wbG90KERlbWFuZCkgKwogIGxhYnMoCiAgICB5ID0gIkRlbWFuZCAoR1cpIiwKICAgIHRpdGxlID0gIkhhbGYtaG91cmx5IGVsZWN0cmljaXR5IGRlbWFuZDogVmljdG9yaWEiCiAgKQpgYGAKYGBge3J9CnZpY19lbGVjICU+JQogIGZpbHRlcih5ZWFyKFRpbWUpID09IDIwMTMpICU+JQogIGF1dG9wbG90KFRlbXBlcmF0dXJlKSArCiAgbGFicygKICAgIHkgPSAiVGVtcGVyYXR1cmUgKGRlZ3JlZXMgQ2Vsc2l1cykiLAogICAgdGl0bGUgPSAiSGFsZi1ob3VybHkgdGVtcGVyYXR1cmVzOiBNZWxib3VybmUsIEF1c3RyYWxpYSIKICApCmBgYAoKYGBge3J9CnZpY19lbGVjICU+JQogIGZpbHRlcih5ZWFyKFRpbWUpID09IDIwMTMpICU+JQogIGdncGxvdChhZXMoeCA9IFRlbXBlcmF0dXJlLCB5ID0gRGVtYW5kKSkgKyAKIyAgZ2VvbV9kZW5zaXR5MmQoKSArCiAgZ2VvbV9wb2ludChzaXplPTAuMSwgYWVzKGNvbG91cj1Ib2xpZGF5KSwgYWxwaGEgPSAwLjQpICsKICBsYWJzKHkgPSAiRGVtYW5kIChHVykiLCB4ID0gIlRlbXBlcmF0dXJlIChkZWdyZWVzIENlbHNpdXMpIikKYGBgCkEgU2NhdHRlcnBsb3QgbWF0cml4CgpgYGB7ciwgd2FybmluZz1GQUxTRX0KdmljX2VsZWMKCmJveHBsb3QodmljX2VsZWMkVGVtcGVyYXR1cmUpCgojIGluc3RhbGwucGFja2FnZXMoIkdHYWxseSIpCnZpY19lbGVjICU+JSAKICAjIG11dGF0ZShUZW1wZXJhdHVyZSA9IHJvdW5kKFRlbXBlcmF0dXJlKSkgJT4lCiAgIyBwaXZvdF93aWRlcih2YWx1ZXNfZnJvbT1jKERlbWFuZCxUZW1wZXJhdHVyZSksIG5hbWVzX2Zyb209SG9saWRheSkgJT4lCiAgR0dhbGx5OjpnZ3BhaXJzKGNvbHVtbnMgPSAzOjIpCgp2aWNfZWxlYyAlPiUgCiAgbXV0YXRlKFllYXIgPSBmYWN0b3IoeWVhcihEYXRlKSkpICU+JQogIHNlbGVjdCgtYyhEYXRlLCBIb2xpZGF5KSkgJT4lCiAgR0dhbGx5OjpnZ3BhaXJzKGNvbHVtbnMgPSA0OjIpCmBgYAoKIyBFeGFtcGxlOiBBdXN0cmFsaWFuIHByb2R1Y3Rpb24KCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQojIGluc3RhbGwucGFja2FnZXMoJ3RzaWJibGVkYXRhJykKbGlicmFyeSh0c2liYmxlZGF0YSkKCmF1c19wcm9kdWN0aW9uCgphdXNfcHJvZHVjdGlvbiAlPiUgZ2dfc2Vhc29uKEVsZWN0cmljaXR5KQoKYXVzX3Byb2R1Y3Rpb24gJT4lIGdnX3NlYXNvbihCZWVyKQpgYGAKCiMgRXhhbXBsZTogR3Jvc3MgRG9tZXN0aWMgUHJvZHVjdCBkYXRhCgojIyBFeHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzCgpgYGB7cn0KbGlicmFyeSh0c2liYmxlZGF0YSkgIyBEYXRhIHNldHMgcGFja2FnZQoKcHJpbnQoZ2xvYmFsX2Vjb25vbXkpCmBgYAoKYGBge3J9Cmdsb2JhbF9lY29ub215ICU+JSBmaWx0ZXIoQ291bnRyeT09IlN3ZWRlbiIpICU+JSBwcmludCgpCmBgYAoKYGBge3J9Cmdsb2JhbF9lY29ub215ICU+JQogIGZpbHRlcihDb3VudHJ5PT0iU3dlZGVuIikgJT4lCiAgYXV0b3Bsb3QoR0RQKSArCiAgZ2d0aXRsZSgiR0RQIGZvciBTd2VkZW4iKSArIHlsYWIoIiRVUyBiaWxsaW9ucyIpCmBgYAoKIyMgRml0dGluZyBkYXRhIHRvIHNpbXBsZSBtb2RlbHMKCmBgYHtyfQpnbG9iYWxfZWNvbm9teSAlPiUgbW9kZWwodHJlbmRfbW9kZWwgPSBUU0xNKEdEUCB+IHRyZW5kKCkpKSAtPiBmaXQKCmZpdAoKCmBgYAoKYGBge3J9CmZpdCAlPiUgZmlsdGVyKENvdW50cnkgPT0gIlN3ZWRlbiIpICU+JSByZXNpZHVhbHMoKQpgYGAKCmBgYHtyfQoKZml0ICU+JSBmaWx0ZXIoQ291bnRyeSA9PSAiU3dlZGVuIikgJT4lIHJlc2lkdWFscygpICU+JSBhdXRvcGxvdCgucmVzaWQpCmBgYAoKIyMjIFdvcmsgd2l0aCBsbihHRFApCgpgYGB7cn0KZ2xvYmFsX2Vjb25vbXkgJT4lCiAgZmlsdGVyKENvdW50cnk9PSJTd2VkZW4iKSAlPiUKICBhdXRvcGxvdChsb2coR0RQKSkgKwogIGdndGl0bGUoImxuKEdEUCkgZm9yIFN3ZWRlbiIpICsgeWxhYigiJFVTIGJpbGxpb25zIikKYGBgCgpgYGB7cn0KZ2xvYmFsX2Vjb25vbXkgJT4lCiAgbW9kZWwodHJlbmRfbW9kZWwgPSBUU0xNKGxvZyhHRFApIH4gdHJlbmQoKSkpIC0+IGxvZ2ZpdApgYGAKCmBgYHtyfQpsb2dmaXQgJT4lIGZpbHRlcihDb3VudHJ5ID09ICJTd2VkZW4iKSAlPiUgcmVzaWR1YWxzKCkgJT4lIGF1dG9wbG90KCkKYGBgCgpgYGB7cn0KZ2xvYmFsX2Vjb25vbXkgJT4lIG1vZGVsKHRyZW5kX21vZGVsID0gVFNMTShsb2coR0RQKSB+IGxvZyhQb3B1bGF0aW9uKSkpIC0+IGZpdDMKCmZpdDMgJT4lIGZpbHRlcihDb3VudHJ5ID09ICJTd2VkZW4iKSAlPiUgcmVzaWR1YWxzKCkgJT4lIGF1dG9wbG90KCkKCmBgYAoKIyBQcm9kdWNpbmcgZm9yZWNhc3RzCgpgYGB7cn0KZml0ICU+JSBmb3JlY2FzdChoID0gIjMgeWVhcnMiKSAtPiBmY2FzdDN5cnMKCmZjYXN0M3lycwoKYGBgCgpgYGB7cn0KCmZjYXN0M3lycyAlPiUgZmlsdGVyKENvdW50cnkgPT0gIlN3ZWRlbiIsIFllYXIgPT0gMjAyMCkgJT4lIHN0cigpCmBgYAoKYGBge3IgdmlzdWFsaXplIGZvcmVjYXN0c30KZmNhc3QzeXJzICU+JSAKICBmaWx0ZXIoQ291bnRyeT09IlN3ZWRlbiIpICU+JQogIGF1dG9wbG90KGdsb2JhbF9lY29ub215KSArCiAgZ2d0aXRsZSgiR0RQIGZvciBTd2VkZW4iKSArIHlsYWIoIiRVUyBiaWxsaW9ucyIpCmBgYAoKIyMgTW9kZWwgcmVzaWR1YWxzIHZzLiBmb3JlY2FzdCBlcnJvcnMKCk1vZGVsIHJlc2lkdWFsczoKCllvdXIgZGF0YTogJHlfMSwgeV8yLCBcbGRvdHMsIHlfVCQKCkZpdHRlZCB2YWx1ZXM6ICRcaGF0e3l9XzEsIFxoYXR7eX1fMiwgXGxkb3RzLCBcaGF0e3l9X1QkCgpNb2RlbCByZXNpZHVhbHM6ICRlX3QgPSB5X3QgLSBcaGF0e3l9X3QkCgpGb3JlY2FzdCBlcnJvcnM6CgpgYGB7cn0KYXVnbWVudChmaXQpCmBgYAoKYGBge3J9CmF1Z21lbnQoZml0KSAlPiUgZmlsdGVyKENvdW50cnkgPT0gIlN3ZWRlbiIpICU+JQogIGdncGxvdChhZXMoeCA9IC5yZXNpZCkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMjApICsKICBnZ3RpdGxlKCJIaXN0b2dyYW0gb2YgcmVzaWR1YWxzIikKYGBgCgojIyBBcmUgdGhlIG1vZGVsIHJlc2lkdWFscyBhdXRvLWNvcnJlbGF0ZWQ/CgpgYGB7cn0KYXVnbWVudChmaXQpICU+JSBmaWx0ZXIoQ291bnRyeSA9PSAiU3dlZGVuIikgLT4gYXVnU3dlZGVuCgphdWdTd2VkZW4gJT4lCiAgQUNGKC5yZXNpZCkgJT4lCiAgYXV0b3Bsb3QoKSArIGdndGl0bGUoIkFDRiBvZiByZXNpZHVhbHMiKQpgYGAKCmBgYHtyfQphdWdtZW50KGZpdDMpICU+JSBmaWx0ZXIoQ291bnRyeSA9PSAiU3dlZGVuIikgLT4gYXVnU3dlZGVuMwoKYXVnU3dlZGVuMyAlPiUKICBBQ0YoLnJlc2lkKSAlPiUKICBhdXRvcGxvdCgpICsgZ2d0aXRsZSgiQUNGIG9mIHJlc2lkdWFscyIpCmBgYAoKCiMgRXhhbXBsZTogR0RQLCBzZXZlcmFsIGNvdW50cmllcwoKCmBgYHtyfQpsaWJyYXJ5KHRzaWJibGVkYXRhKSAjIERhdGEgc2V0cyBwYWNrYWdlCgpub3JkaWMgPC0gYygiU3dlZGVuIiwgIkRlbm1hcmsiLCAiTm9yd2F5IiwgIkZpbmxhbmQiKQoKKGdsb2JhbF9lY29ub215ICU+JSBmaWx0ZXIoQ291bnRyeSAlaW4lIG5vcmRpYykgLT4gbm9yZGljX2Vjb25vbXkpCgpgYGAKCmBgYHtyfQpub3JkaWNfZWNvbm9teSAlPiUgYXV0b3Bsb3QoR0RQKQpgYGAKCmBgYHtyfQpmaXRub3JkIDwtIG5vcmRpY19lY29ub215ICU+JQogIG1vZGVsKAogICAgdHJlbmRfbW9kZWwgPSBUU0xNKEdEUCB+IHRyZW5kKCkpLAogICAgdHJlbmRfbW9kZWxfbG4gPSBUU0xNKGxvZyhHRFApIH4gdHJlbmQoKSksCiAgICBldHMgPSBFVFMoR0RQIH4gdHJlbmQoIkEiKSksCiAgICBhcmltYSA9IEFSSU1BKEdEUCkKICApCgpmaXRub3JkCmBgYAoKYGBge3J9CmZpdG5vcmQgJT4lCiAgc2VsZWN0KGFyaW1hKSAlPiUKICBjb2VmKCkKYGBgCgoKRGVubWFyazogQVJNQSgxLDEpCgpGaW5sYW5kOiBNQSgyKQoKTm9yd2F5OiBNQSgxKQoKU3dlZGVuOiBNQSgyKQoKYGBge3J9Cm5vcmRpY19lY29ub215ICU+JQogIG1vZGVsKGFyaW1hX2NvbnN0cmFpbmVkID0gQVJJTUEoR0RQIH4gcGRxKDEsMCwyKSkpICU+JSBzZWxlY3QoYXJpbWFfY29uc3RyYWluZWQpICU+JSBjb2VmKCkKYGBgCgpgYGB7cn0KZml0bm9yZCAlPiUgY29lZigpIApgYGAKCmBgYHtyfQpmaXRub3JkICU+JSAgZ2xhbmNlKCkgIApgYGAKCmBgYHtyfQpmaXRub3JkICU+JSBmaWx0ZXIoQ291bnRyeSA9PSAiRGVubWFyayIpICU+JSBzZWxlY3QoYXJpbWEpICU+JSByZXBvcnQoKQpgYGAKCmBgYHtyfQpmaXRub3JkICU+JQogIGFjY3VyYWN5KCkgJT4lCiAgYXJyYW5nZShDb3VudHJ5LCBNUEUpCmBgYAoKCgpgYGB7ciwgZXZhbD1GQUxTRX0KIyBFVFMgZm9yZWNhc3RzClVTQWNjRGVhdGhzICU+JQogIGV0cygpICU+JQogIGZvcmVjYXN0KCkgJT4lCiAgYXV0b3Bsb3QoKQpgYGAKCmBgYHtyLCBldmFsPUZBTFNFfQpzdHIodGF5bG9yKQpwbG90KHRheWxvcikKYGBgCgojIyBQbG90IGxhZ2dlZCB2YWx1ZXMKCmBgYHtyIHBsb3QgbGFnZ2VkIHZhbHVlc30KdmFlbHNhbGVzX3RibF90cyAgJT4lIGZpbHRlcihtb250aChNb250aCkgJWluJSBjKDMsNiw5LDEyKSkgJT4lIGdnX2xhZyhzYWxlc19HV2gsIGxhZ3MgPSAxOjIpCgp2YWVsc2FsZXNfdGJsX3RzICAlPiUgZmlsdGVyKG1vbnRoKE1vbnRoKSA9PSAxKSAlPiUgZ2dfbGFnKHNhbGVzX0dXaCwgbGFncyA9IDE6MikKYGBgCgpgYGB7cn0KdmFlbHNhbGVzX3RibF90cyAlPiUgQUNGKHNhbGVzX0dXaCkgJT4lIGF1dG9wbG90KCkKYGBgCgpgYGB7ciBwZXJmb3JtIGF1dG9tYXRlZCB0aW1lIHNlcmllcyBkZWNvbXBvc2l0aW9ufQoKCiMgZGVjb21wb3NlKHZhZWxzYWxlc190YmxfdHMpCmBgYAoKYGBge3IgcGVyZm9ybSBhZGRpdGl2ZSBTVEwgZGVjb21wb3NpdGlvbiBvZiB0aGUgVkEgZWxlY3RyaWNpdHkgc2FsZXMgdGltZSBzZXJpZXN9CnZhZWxzYWxlc190YmxfdHMgJT4lCiAgbW9kZWwoU1RMKHNhbGVzX0dXaCB+IHRyZW5kKHdpbmRvdz0yMSkgKyBzZWFzb24od2luZG93PSdwZXJpb2RpYycpLCByb2J1c3QgPSBUUlVFKSkgJT4lCiAgY29tcG9uZW50cygpICU+JQogIGF1dG9wbG90KCkKYGBgCgpgYGB7ciBwZXJmb3JtIG11bHRpcGxpY2F0aXZlIFNUTCBkZWNvbXBvc2l0aW9uIG9mIHRoZSBWQSBlbGVjdHJpY2l0eSBzYWxlcyB0aW1lIHNlcmllc30KdmFlbHNhbGVzX3RibF90cyAlPiUKICBtdXRhdGUobG5fc2FsZXNfR1doID0gbG9nKHNhbGVzX0dXaCkpICU+JQogIG1vZGVsKFNUTChsbl9zYWxlc19HV2ggfiB0cmVuZCh3aW5kb3c9MjEpICsgc2Vhc29uKHdpbmRvdz0ncGVyaW9kaWMnKSwKICAgIHJvYnVzdCA9IFRSVUUpKSAlPiUKICBjb21wb25lbnRzKCkgJT4lCiAgYXV0b3Bsb3QoKQpgYGAKCmBgYHtyfQp2YWVsc2FsZXNfdGJsX3RzICU+JQogIGZlYXR1cmVzKHNhbGVzX0dXaCwgZmVhdF9zdGwpCmBgYAoKYGBge3J9CnZhZWxzYWxlc190YmxfdHMgJT4lCiAgZmVhdHVyZXMoc2FsZXNfR1doLCBmZWF0dXJlX3NldChwa2dzPSJmZWFzdHMiKSkKYGBgCgoKCiMgUmVmZXJlbmNlcwo=